Skip to content

feat(editor): Python autocomplete for torch, numpy and tensor methods - #35

Open
xiningli wants to merge 1 commit into
whwangovo:mainfrom
xiningli:feat/python-completions
Open

xiningli wants to merge 1 commit into
whwangovo:mainfrom
xiningli:feat/python-completions

Conversation

@xiningli

@xiningli xiningli commented Aug 23, 2026

Copy link
Copy Markdown

The problem

Monaco ships a Python tokenizer, not a language service. The editor's only suggestions come from the built-in word-based provider, i.e. words that already appear in the current buffer. So typing np. or torch. offers nothing, and the contrast with the IDE most people arrive from is pretty stark — especially in a tool whose whole premise is "write the implementation yourself".

The fix

A completion provider backed by a generated dataset, in two pieces.

scripts/gen_completions.py introspects the exact modules the grading sandbox injects into every submission — grading_service/main.py binds torch, nn, F, np, math, Tensor — and emits labels, kinds, signatures and one-line docstring summaries. Scoping it to the sandbox namespace means the editor can never suggest something the grader would reject.

It follows the existing export_problems.py / build_solutions.py convention: the artifact is committed so contributors don't need torch installed to build the web app. Re-run it after upgrading torch or numpy.

$ python scripts/gen_completions.py
Wrote 2528 completions across 6 namespaces to web/src/lib/pythonCompletions.json (310 KB)
  torch     1025
  nn         182
  F          160
  np         497
  math        60
  Tensor     604

web/src/lib/pythonCompletions.ts registers the provider on .:

  • Resolves the receiver left of the dot, including the long forms people type out of habit — torch.nn.functional., numpy., torch.Tensor. all map to the right namespace.
  • For an unrecognised receiver it falls back to Tensor members. In these problems nearly every local is a tensor, and the alternative is the word-based provider offering other identifiers from the file. This is a heuristic and is commented as one.
  • Suppresses suggestions inside strings and comments.
  • Registered once globally rather than per editor instance, so mounting a second editor doesn't produce duplicate entries.

Word-based suggestions are kept for local variables, with localityBonus so nearby names still rank well.

Bundle size

The dataset is ~310 KB, so it is imported dynamically. It lands in the same lazy chunk as the editor (already dynamic(..., { ssr: false })) and never reaches the initial page bundle. Verified against next build:

  • first-load shared chunks: 51-*.js 45.5 kB and 88b0a408-*.js 54.2 kB — unchanged, and grep logsumexp finds 0 hits in either
  • the payload is code-split into its own chunk, fetched when the editor mounts
  • /problems/[id] First Load JS: 165 kB, unchanged

Gzipped over the wire it is ~53 KB, once, cached thereafter.

Testing

tsc --noEmit clean, next build clean.

I also drove the provider headlessly with a stubbed monaco object to check the resolution logic rather than just the types — 17 assertions covering each namespace, the dotted aliases, the Tensor fallback, top-level suggestions, and string/comment suppression:

PASS  "np." -> array, zeros, linalg, argmax, pi
PASS  "    x = np.ze" -> zeros, zeros_like
PASS  "torch." -> softmax, logsumexp, stft, cdist, float32
PASS  "F." -> cross_entropy, normalize, one_hot
PASS  "nn." -> Linear, Conv2d, LayerNorm
PASS  "torch.nn.functional." -> cross_entropy, softmax
PASS  "torch.Tensor." -> unsqueeze, backward
PASS  "math." -> sqrt, log10, pi
PASS  "numpy." -> array, zeros
PASS  "residual." falls back to Tensor members
PASS  top level -> globals + keywords + builtins
PASS  comment suppressed
PASS  open double-quote suppressed
PASS  open single-quote suppressed
PASS  closed string NOT suppressed
PASS  torch namespace non-trivial (1025 entries)
PASS  item carries kind + detail + docs + range

sample -> np.zeros(shape, dtype=None, order='C', *, device=None, like=None)
          Return a new array of given shape and type, filled with zeros.

Known limitations

  • No type inference. x. after x = model(...) guesses Tensor members. A real language service (Pyright in a web worker) would do better, but that is a much larger change and a much larger bundle.
  • The string/comment check inspects only the current line, so it does not track triple-quoted blocks spanning lines. Cheap and good enough for a code cell; noted in the source.
  • Signatures come from inspect.signature with a docstring fallback for C extensions. A handful of torch builtins expose neither and get no detail.

Happy to split the generator and the provider into separate commits, trim the namespaces, or drop the README touch-ups if you'd prefer a tighter diff.

Monaco ships a Python tokenizer but no language service, so the editor's
only suggestions were words already present in the buffer -- typing `np.`
or `torch.` offered nothing, which is a rough contrast with the IDE most
people are used to.

This registers a completion provider backed by a generated dataset:

- `scripts/gen_completions.py` introspects the exact modules the grading
  sandbox injects into every submission (`grading_service/main.py`:
  torch, nn, F, np, math, Tensor) and emits signatures plus one-line
  docstring summaries. Following the existing `export_problems.py` /
  `build_solutions.py` convention, the artifact is committed so
  contributors don't need to regenerate it; re-run it after a torch or
  numpy upgrade.
- `web/src/lib/pythonCompletions.ts` resolves the receiver left of the
  dot, including the long forms people type out of habit
  (`torch.nn.functional.`, `numpy.`). For an unrecognised receiver it
  falls back to Tensor members, since in these problems nearly every
  local is a tensor -- and the alternative is other words in the file.
  Suggestions are suppressed inside strings and comments.

2,528 completions, ~310 KB. The data is imported dynamically so it lands
in the same lazy chunk as the editor and stays out of the initial page
bundle -- verified against `next build`: the first-load shared chunks are
unchanged and the payload is code-split into its own chunk.

Word-based suggestions are kept for local variables, with `localityBonus`
so nearby names still rank well.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants